package edu.hawaii.myisern.commandline;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JTable;

/**
 * Provides the command line interface for the MyISERN system. This program can be
 * executed by the following commands: ant jar java -jar myisern-1-red.jar.
 * 
 * @author Phuoc Le
 * @author Ka Yee Leung
 * @author Jared Sunouchi
 */
public class MyIsernCommandLineInterface {
  
  private static InputStreamReader streamReader = new InputStreamReader(System.in);
  private static BufferedReader reader = new BufferedReader(streamReader);
  
  /** first arguments */
  private static final String listCollaborations = "-listCollaborations";
  private static final String listOrganizations = "-listOrganizations";
  private static final String describe = "-describe";
  private static final String display = "-display";
  private static final String input = "-input";
  private static final String quit = "-quit";
  
  /** second arguments */
  private static final String collaboration = "-collaboration";
  private static final String organization = "-organization";
  private static final String researcher = "-researcher";
  private static final String all = "-all";
  private static final String collaborationLevelEquals = "-collaborationLevelEquals";
  private static final String collaborationLevelGreaterThan = "-collaborationLevelGreaterThan";
  
  /** third arguments */
  private static final String uniqueId = "<uniqueID>";
  private static final String year = "<year>";
  private static final String integer = "<integer>";
  private static final String collaborations = "Collaborations";
  private static final String organizations = "Organizations";
  private static final String researchers = "Researchers";
  
  /**
   * Tests the command line by reading input from a file and loading XML data from a source.
   * 
   * @param file The file containing the input data.
   * @param loader The object containing the XML data.
   * @throws Exception If problems occur.
   */
  public static void setBufferedReaderAndRun(FileReader file, MyIsernXmlLoader loader)
      throws Exception {
    reader = new BufferedReader(file);
    run(new String[0], loader);
  }
  
  /**
   * Runs the command line interface for the MyISERN system.
   * 
   * @param args Input arguments.
   * @throws Exception If problems occur.
   */
  public static void main(String[] args) throws Exception {
    System.out.println("Loading data...");
    MyIsernXmlLoader loader = new MyIsernXmlLoader();
    String[] newArgs = args;
    if ((args.length > 0) && ("-loadTest".equals(args[args.length - 1]))) {
      String userDir = "user.dir";
      System.out.println("Loading data...");

      File collaborationsFile = new File(System.getProperty(userDir)
          + "/xml/tests/test.collaborations.example.xml");
      File organizationsFile = new File(System.getProperty(userDir)
          + "/xml/tests/test.organizations.example.xml");
      File researchersFile = new File(System.getProperty(userDir)
          + "/xml/tests/test.researchers.example.xml");

      loader = new MyIsernXmlLoader(collaborationsFile, organizationsFile, researchersFile);

      newArgs = new String[args.length - 1];
      for (int i = 0; i < (args.length - 1); i++) {
        newArgs[i] = args[i];
      }
    }
    
    run(newArgs, loader);
  }
  
  /**
   * Runs the command line interface.
   * 
   * @param args The arguments provided by the user.
   * @param loader The object storing the XML file data.
   * @throws Exception If problems occur.
   */
  public static void run(String[] args, MyIsernXmlLoader loader) throws Exception {    
    // list of all possible first arguments with a description
    Map<String, String> firstArguments = new HashMap<String, String>();
    firstArguments.put(listOrganizations, "Lists the Organizations by collaboration level.");
    firstArguments.put(describe, "Describes collaborations, researchers, or organizations.");
    firstArguments.put(listCollaborations, "Lists the collaborations done by organization, " +
    		"year, or researcher.");
    firstArguments.put(input, "Add or edit a collaboration, organization, researcher.");
    firstArguments.put(display, "Display the collaboration, organization, " +
    		"and researcher tables.");
    firstArguments.put(quit, "Exit.");
    
    // list of all possible second arguments with a description
    Map<String, String> secondArguments = new HashMap<String, String>();
    secondArguments.put(collaborationLevelEquals, "List organizations by the number of " +
    		"collaborations equal to an integer");
    secondArguments.put(collaborationLevelGreaterThan, "List organizations by the number of " +
        "collaborations greater than an integer");
    secondArguments.put(collaboration, "Specifies that collaboration is the chosen object " +
    		"type.");
    secondArguments.put(organization, "Specifies that organization is the chosen object type.");
    secondArguments.put(researcher, "Specifies that researcher is the chosen object type.");
    secondArguments.put(all, "Specifies that all objects of a certain type will be described.");
    secondArguments.put(year, "Specify the collaboration by a year.");
    
    // list of all possible third arguments with a description
    Map<String, String> thirdArguments = new HashMap<String, String>();
    thirdArguments.put(collaborations, "Specifies that all collaborations will be described.");
    thirdArguments.put(organizations, "Specifies that all organizations will be described.");
    thirdArguments.put(researchers, "Specifies that all researchers will be described.");
    thirdArguments.put(integer, "Integer argument.");
    thirdArguments.put(uniqueId, "Unique ID string.");
    
    // list of all possible complete commands
    List<List<String>> completeCommandList = new ArrayList<List<String>>();
    // -listOrganizations
    List<String> commandList = new ArrayList<String>();
    commandList.add(listOrganizations);
    commandList.add(collaborationLevelEquals);
    commandList.add(integer);    
    completeCommandList.add(commandList);
    
    commandList = new ArrayList<String>();
    commandList.add(listOrganizations);
    commandList.add(collaborationLevelGreaterThan);
    commandList.add(integer);
    completeCommandList.add(commandList);
    
    // -describe
    commandList = new ArrayList<String>();
    commandList.add(describe);
    commandList.add(researcher);
    commandList.add(uniqueId);
    completeCommandList.add(commandList);
   
    commandList = new ArrayList<String>();
    commandList.add(describe);
    commandList.add(organization);
    commandList.add(uniqueId);
    completeCommandList.add(commandList);
    
    commandList = new ArrayList<String>();
    commandList.add(describe);
    commandList.add(collaboration);
    commandList.add(uniqueId);
    completeCommandList.add(commandList);
    
    commandList = new ArrayList<String>();
    commandList.add(describe);
    commandList.add(all);
    commandList.add(collaborations);
    completeCommandList.add(commandList);
    
    commandList = new ArrayList<String>();
    commandList.add(describe);
    commandList.add(all);
    commandList.add(organizations);
    completeCommandList.add(commandList);
    
    commandList = new ArrayList<String>();
    commandList.add(describe);
    commandList.add(all);
    commandList.add(researchers);
    completeCommandList.add(commandList);
    
    // -listCollaborations
    commandList = new ArrayList<String>();
    commandList.add(listCollaborations);
    commandList.add(collaboration);
    commandList.add(uniqueId);
    completeCommandList.add(commandList);
    
    commandList = new ArrayList<String>();
    commandList.add(listCollaborations);
    commandList.add(organization);
    commandList.add(year);
    completeCommandList.add(commandList);
    
    commandList = new ArrayList<String>();
    commandList.add(listCollaborations);
    commandList.add(researcher);
    commandList.add(uniqueId);
    completeCommandList.add(commandList);

    System.out.println("Data has been loaded. Initializing the system...");
    System.out.println("Validating data...");
    if (!(loader.sanityCheck())) {
      System.out.println("There are some errors that must be corrected using the web application");
      //return;
    }
    
    String command = "";
    
    if (args.length > 0) {
      command = args[0];
    }
    else {
      command = "";
    }
    while (notValidFirstCommand(firstArguments, command)) {
      System.out.println();
      System.out.print("Command: ");
      command = reader.readLine();
      command = command.trim();    
    }
    
    if (command.equals(quit)) {
      return;
    }
    else if (command.equals(display)) { 
      MyIsernTableCreator.createAndShowAllTables(loader);
    }
    else if (command.equals(input)) {
      MyIsernInput.runInput(loader);
    }
    else {
      String firstArg = command;
      String secondArg = "";
      String thirdArg = "";
      if (args.length > 1) {
        command = args[1];
      }
      else {
        command = "";
      }
      while (notValidSecondCommand(secondArguments, firstArg, command, completeCommandList)) {
        System.out.println();
        System.out.print("Command: ");
        command = reader.readLine();
        command = command.trim();
        if (command.equals(quit)) {
          return;
        }
      }
      secondArg = command;

      if (args.length > 2) {
        command = args[2];
      }
      else {
        command = "";
      }
      while (notValidThirdCommand(thirdArguments, firstArg, secondArg, command, 
          completeCommandList)) {
        System.out.println();
        System.out.print("Command: ");
        command = reader.readLine();
        command = command.trim();
        if (command.equals(quit)) {
          return;
        }
      }
      thirdArg = command;

      callFunction(firstArg, secondArg, thirdArg, loader);
    }
  }
  
  /**
   * Checks if the command is a valid first argument.
   * 
   * @param firstArguments The list of the valid first arguments and their descriptions.
   * @param firstArg The first argument supplied by the user.
   * @return Whether the first argument is valid or not.
   */
  public static boolean notValidFirstCommand(Map<String, String> firstArguments, 
      String firstArg) {
    boolean found = !firstArguments.containsKey(firstArg);
    if (found) {
      System.out.println();
      System.out.println("List of supported commands:");
      System.out.println();
      
      printCommandAndDescription(firstArguments);
      
      return true;
    }
    else {
      return false;
    }
  }
  
  /**
   * Checks if the command is a valid second argument, given the value of the first argument.
   * 
   * @param secondArguments The list of the valid second arguments and their descriptions.
   * @param firstArg The first argument supplied by the user.
   * @param secondArg The second argument supplied by the user.
   * @param commandList The complete list of all commands.
   * @return Whether the second argument is valid or not.
   */
  public static boolean notValidSecondCommand(Map<String, String> secondArguments, String firstArg, 
      String secondArg, List<List<String>> commandList) {
    List<List<String>> commandRemoveList = new ArrayList<List<String>>();
    List<List<String>> commandListCopy = commandList;
    for (List<String> command : commandListCopy) {
      if (!((command.get(0)).equals(firstArg))) {
        commandRemoveList.add(command);
      }
    }
    commandListCopy.removeAll(commandRemoveList);
    
    // find the valid commands with the 2 arguments
    List<String> availableCommands = new ArrayList<String>();
    List<List<String>> chosenCommands = new ArrayList<List<String>>();
    for (List<String> command : commandListCopy) {
      if ((command.get(1)).equals(secondArg)) {
        chosenCommands.add(command);
      }
      availableCommands.add(command.get(1));
    }
    
    // print out the supported commands given the first argument
    if (chosenCommands.isEmpty()) {
      System.out.println();
      System.out.println("List of supported commands with first argument as " + firstArg + ":");
      System.out.println();
      
      Map<String, String> secondArgumentsCopy = removeInvalidArguments(secondArguments, 
          availableCommands);
      
      printCommandAndDescription(secondArgumentsCopy);
      
      return true;
    }
    return false;
  }
  
  /**
   * Checks if the command is a valid third argument, given the first and second arguments.
   * 
   * @param thirdArguments The list of the valid third arguments and their descriptions.
   * @param firstArg The first argument supplied by the user.
   * @param secondArg The second argument supplied by the user.
   * @param thirdArg The third argument supplied by the user.
   * @param commandList The complete list of all commands.
   * @return Whether the third argument is valid or not.
   */
  public static boolean notValidThirdCommand(Map<String, String> thirdArguments, String firstArg, 
      String secondArg, String thirdArg, List<List<String>> commandList) {
    List<List<String>> commandRemoveList = new ArrayList<List<String>>();
    List<List<String>> commandListCopy = commandList;
    for (List<String> command : commandListCopy) {
      if (!((command.get(0)).equals(firstArg)) || !((command.get(1)).equals(secondArg))) {
        commandRemoveList.add(command);
      }
    }
    commandListCopy.removeAll(commandRemoveList);
    
    // find the valid commands with the 3 arguments
    List<String> availableCommands = new ArrayList<String>();
    List<List<String>> chosenCommands = new ArrayList<List<String>>();
    for (List<String> command : commandListCopy) {
      if ((command.get(2)).equals(thirdArg)) {
        chosenCommands.add(command);
      }
      else if ((command.get(2)).equals(uniqueId)) {
        if (!("".equals(thirdArg))) {
          chosenCommands.add(command);
        }
      }
      else if ((((command.get(2)).equals(year)) || ((command.get(2)).equals(integer))) && 
          (!("".equals(thirdArg)))) {
        chosenCommands.add(command);
      }
      availableCommands.add(command.get(2));
    }
    
    // print out the supported commands given the first and second argument
    if (chosenCommands.isEmpty()) {
      System.out.println();
      System.out.println("List of supported commands with the " + firstArg + " " + secondArg + 
          " argument order:");
      System.out.println();
      
      Map<String, String> thirdArgumentsCopy = removeInvalidArguments(thirdArguments, 
          availableCommands);
      
      printCommandAndDescription(thirdArgumentsCopy);
      
      return true;
    }
    return false;
  }
  
  /**
   * Removes the commands that are not valid given the supplied arguments.
   * 
   * @param arguments The list of all arguments at a given level (first, second, third).
   * @param availableArguments The list of all the available arguments that can be used.
   * @return The modified list of all arguments at a given level that includes only the valid ones.
   */
  public static Map<String, String> removeInvalidArguments(Map<String, String> arguments, 
      List<String> availableArguments) {
    Set<Map.Entry<String, String>> set = arguments.entrySet();
    Map<String, String> argumentsCopy = arguments;
    List<String> keys = new ArrayList<String>();
    for (Map.Entry<String, String> command : set) {
      if (!(availableArguments.contains(command.getKey()))) {
        keys.add(command.getKey());
      }
    }
    for (String key : keys) {
      argumentsCopy.remove(key);
    }
    
    return argumentsCopy;
  }
  
  /**
   * Prints out the valid arguments and their descriptions.
   * 
   * @param commands The valid arguments and their descriptions.
   */
  public static void printCommandAndDescription(Map<String, String> commands) {
    Set<Map.Entry<String, String>> set = commands.entrySet();
    
    int maxLength = 0;
    for (Map.Entry<String, String> command : set) {
      if (command.getKey().length() > maxLength) {
        maxLength = command.getKey().length();
      }
    }
    maxLength = maxLength + 3;

    for (Map.Entry<String, String> command : set) {
      int currentLength = 0;
      currentLength = command.getKey().length();
      StringBuffer whiteSpace = new StringBuffer();
      for (int i = 0; i < (maxLength - currentLength); i++) {
        String space = " ";
        whiteSpace.append(space);
      }
      System.out.println(command.getKey() + whiteSpace.toString() + command.getValue());
    }
  }
  
  /**
   * Calls the MyISERN function based on the first, second, and third arguments. 
   * 
   * @param firstArg The first argument provided by the user.
   * @param secondArg The second argument provided by the user.
   * @param thirdArg The third argument provided by the user.
   * @param loader The MyISERN information container.
   * @throws Exception If problems occur.
   */
  public static void callFunction(String firstArg, String secondArg, String thirdArg, 
      MyIsernXmlLoader loader) throws Exception {
    MyIsernQueries queries = new MyIsernQueries(loader);
    if (firstArg.equals(listOrganizations)) {
      String list = queries.listOrganizations(secondArg, Integer.parseInt(thirdArg));
      if ("".equals(list)) {
        System.out.println("No matching organizations found.");
      }
      else {      
        System.out.print(list);
      }
    }
    if (firstArg.equals(describe)) {
      if (secondArg.equals(all)) {
        if (thirdArg.equals(researchers)) {
          MyIsernTableCreator.createAndShowResearchersTable(loader);
        }
        else if (thirdArg.equals(organizations)) {
          MyIsernTableCreator.createAndShowOrganizationsTable(loader);
        }
        else if (thirdArg.equals(collaborations)) {
          MyIsernTableCreator.createAndShowCollaborationsTable(loader);
        }
      }
      else {
        if (loader.getUniqueIds().containsKey(thirdArg)) {
          String summary = queries.generateDescription(thirdArg, secondArg);
          System.out.println(summary);
        }
        else {
          System.out.println("The unique ID: " + thirdArg + ", does not exist.");
        }
      }
    }
    if (firstArg.equals(listCollaborations)) {
      JTable table = queries.listCollaborations(thirdArg ,secondArg);
      if (table != null) {
        MyIsernTableCreator.createAndShowCollaborationsList(table, loader);
      }
    }
  }
}
